/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.commons.cryptography;

import io.iec.edp.caf.common.enums.AsymmetricAlgorithmEnum;
import io.iec.edp.caf.commons.cryptography.model.CafKeyPair;
import io.iec.edp.caf.commons.cryptography.providers.DSACryptoProvider;
import io.iec.edp.caf.commons.cryptography.providers.RSACryptoProvider;
import io.iec.edp.caf.commons.cryptography.providers.SM2CryptoProvider;
import io.iec.edp.caf.commons.cryptography.service.AsymmetricCryptoProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import java.security.Security;
import java.util.Base64;

/**
 * 不对称加密/解密工具类
 *
 * @author guowenchang
 */
public class AsymmetricCryptographer {

    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    private AsymmetricCryptoProvider asymmetricCryptoProvider;
    private AsymmetricAlgorithmEnum asymmetricAlgorithmEnum;

    /**
     * 私有构造参数
     *
     * @param asymmetricCryptographyService 加密方式
     */
    private AsymmetricCryptographer(AsymmetricAlgorithmEnum asymmetricCryptographyService) {
        this.asymmetricAlgorithmEnum = asymmetricCryptographyService;
        switch (asymmetricCryptographyService) {
            case SM2:
                this.asymmetricCryptoProvider = new SM2CryptoProvider();
                break;
            case RSA:
                this.asymmetricCryptoProvider = new RSACryptoProvider();
                break;
            case DSA:
                this.asymmetricCryptoProvider = new DSACryptoProvider();
                break;

            default:
                throw new RuntimeException("Unknown Cryptographer");
        }
    }

    /**
     * 公有静态构造器, 返回加密工具实例
     *
     * @param asymmetricAlgorithmEnum 加密方式
     * @return 对象实例
     */
    public static AsymmetricCryptographer getInstance(AsymmetricAlgorithmEnum asymmetricAlgorithmEnum) {
        return new AsymmetricCryptographer(asymmetricAlgorithmEnum);
    }

    public CafKeyPair generateKeyPair() {
        return this.asymmetricCryptoProvider.generateKeyPair();
    }

    /**
     * 使用公钥对明文加密
     *
     * @param byteIn 明文
     * @param publikKey    公钥
     * @return 密文
     */
    public byte[] encrypt(byte[] byteIn, String publikKey) {
        byteIn = appendBottomZero(byteIn, this.asymmetricAlgorithmEnum.getBlockSize());
        byte[] keyByte = getLegalKey(publikKey);
        return asymmetricCryptoProvider.encrypt(byteIn, keyByte,false);
    }

    /**
     * 使用私钥对密文解密
     *
     * @param byteIn 密文
     * @param privateKey    密码
     * @return 明文
     */
    public byte[] decrypt(byte[] byteIn, String privateKey) {
        byte[] keyByte = getLegalKey(privateKey);
        return removeBottomZero(asymmetricCryptoProvider.decrypt(byteIn, keyByte,false));
    }

    /**
     * 使用公钥对明文加密
     *
     * @param stringIn 明文
     * @param publikKey      公钥
     * @return 密文
     */
    public String encrypt(String stringIn, String publikKey) {
        byte[] byteIn = stringIn.getBytes();
        byte[] byteOut = this.encrypt(byteIn, publikKey);
        return Base64.getEncoder().encodeToString(byteOut);
    }

    /**
     * 使用私钥对密文解密
     *
     * @param stringIn 密文
     * @param privateKey      密码
     * @return 明文
     */
    public String decrypt(String stringIn, String privateKey) {
        byte[] byteIn = Base64.getDecoder().decode(stringIn);
        byte[] byteOut = this.decrypt(byteIn, privateKey);
        return new String(byteOut);
    }

    /**
     * 签名
     *
     * @param byteIn 原文
     * @param privateKey    私钥
     * @return 签名
     */
    public String sign(byte[] byteIn, String privateKey) {
        byte[] legalKey = getLegalKey(privateKey);
        byte[] signByte = asymmetricCryptoProvider.sign(byteIn, legalKey);
        return Base64.getEncoder().encodeToString(signByte);
    }

    /**
     * 验签
     *
     * @param byteIn 原文
     * @param sign   签名
     * @param publicKey    公钥
     * @return 是否验证成功
     */
    public boolean verify(byte[] byteIn, String sign, String publicKey) {
        return asymmetricCryptoProvider.verify(byteIn, Base64.getDecoder().decode(sign), getLegalKey(publicKey));
    }

    /**
     * 签名
     *
     * @param stringIn 原文
     * @param privateKey      私钥
     * @return 签名
     */
    public String sign(String stringIn, String privateKey) {
        return this.sign(stringIn.getBytes(), privateKey);
    }

    /**
     * 验签
     *
     * @param stringIn 原文
     * @param sign     签名
     * @param publicKey      公钥
     * @return 是否验证成功
     */
    public boolean verify(String stringIn, String sign, String publicKey) {
        return this.verify(stringIn.getBytes(), sign, publicKey);
    }

    /**
     * 将Base64格式的key字符串转为byte数组
     *
     * @param key 初始密钥
     * @return 合法密钥
     */
    private byte[] getLegalKey(String key) {
        return Base64.getDecoder().decode(key);
    }

    /**
     * 在byte数组后填充0x00 使其可以被分组大小整除
     *
     * @param bytes     明文
     * @param blockSize 分组大小
     * @return
     */
    private byte[] appendBottomZero(byte[] bytes, int blockSize) {
        int length = bytes.length;
        blockSize = blockSize / 8;

        //计算需填充长度
        if (length % blockSize != 0) {
            length = length + (blockSize - (length % blockSize));
        }

        //填充
        byte[] result = new byte[length];
        System.arraycopy(bytes, 0, result, 0, bytes.length);

        return result;
    }

    /**
     * 移除末尾的0x00
     *
     * 这里末尾原先就是0x00的会被误移除，待优化
     *
     * @param bytes
     * @return
     */
    private byte[] removeBottomZero(byte[] bytes) {
        int endPosition = bytes.length;
        for (int i = bytes.length; i > 0; i--) {
            endPosition = i;
            if (bytes[i - 1] != 0x00) {
                break;
            }
        }

        byte[] result = new byte[endPosition];
        System.arraycopy(bytes, 0, result, 0, endPosition);

        return result;
    }
}
